package org.goldenport.g3.events

import org.goldenport.g3.{G3Message, G3Command, G3Event, G3Parcel, G3HttpResponse, G3AgentContext}
import org.goldenport.g3.messages._
import org.goldenport.g3.events.http._

/**
 * @since   Jun. 10, 2010
 * @version Jan. 17, 2011
 * @author  ASAMI, Tomoharu
 */
abstract class Exceptioned(
  uri: String, cause: Option[G3Event] = None,
  val command: Option[G3Command] = None,
  val exception: Option[Exception] = None,
  val message: String = "",
  val stackSliceLength: Int = 20
) extends G3Event(uri, cause) {
  def toHttpStatusEvent: HttpStatusEvent = new Http500(uri, exception, report_message)
  def toHtml: G3HttpResponse = toHttpStatusEvent.toHtml
  def toAtom: G3HttpResponse = toHttpStatusEvent.toAtom
  def toJson(ctx: G3AgentContext, cmd: G3Command): G3HttpResponse = toHttpStatusEvent.toJson(ctx, cmd)

  protected final def report_message = {
    if (message != "") message
    else if (!cause.isEmpty) {
      cause.get.toString // XXX
    } else if (!exception.isEmpty) {
      exception.get.getMessage
    } else if (!command.isEmpty) {
      command.get.toString // XXX
    } else {
      uri
    }
  }
}

class CatchExceptioned(
  uri: String,
  exception: Exception,
  command: Option[G3Command] = None
) extends Exceptioned(uri, command = command, exception = Some(exception)) {
  private def get_location(e: Exception): String = {
    val st = e.getStackTrace()
    get_location(find_app_elements(st))
  }

  private def find_app_elements(stack: Array[StackTraceElement]): Seq[StackTraceElement] = {
    def get_stack_slice(index: Int) = {
      stack.slice(index, index + scala.math.min(stack.length - index, stackSliceLength))
    }

    stack.findIndexOf(ste => {
      val name = ste.getClassName
      !(name.startsWith("java.") || name.startsWith("scala."))
    }) match {
      case index if index != -1 => get_stack_slice(index)
      case _ => get_stack_slice(0)
    }
  }

  private def get_location(stack: Seq[StackTraceElement]): String = {
    stack.map(ste => {
      "%s#%s(%s:%s)".format(
        ste.getClassName,
        ste.getMethodName,
        ste.getFileName,
        ste.getLineNumber
      )
    }).mkString(";")
  }

  override def toString() = {
    "Exception[%s](%s){%s}[%s]".format(
      uri,
      exception.getClass.getName,
      exception.getMessage,
      get_location(exception)
    )
  }
}

class RecordNotValidatedExceptioned(
  uri: String, command: Option[G3Command],
  val record: Record
) extends Exceptioned(uri, command = command) {
  override def toHttpStatusEvent = new Http200(uri, errors = Some(record))
}

/*
class RecordsNotValidatedExceptioned(
  uri: String, command: Option[G3Command],
  val records: RecordSet
) extends Exceptioned(uri, command = command) {
  override def toHttpStatusEvent = new Http400(uri, errors = Some(records))
}
*/

class NotFoundExceptioned(
  uri: String, command: Option[G3Command] = None,
  exception: Option[Exception] = None, message: String = ""
) extends Exceptioned(uri, command = command, exception = exception, message = message) {
  def this(cmd: G3Command) = this(cmd.uri, Some(cmd))

  override def toHttpStatusEvent = new Http404(uri, exception, report_message)
}

object Exceptioned {
  def apply(uri: String, exception: Exception) = {
    new CatchExceptioned(uri, exception)
  }

  def apply(anInput: G3Parcel, anException: Exception) = {
    val uri = get_uri(anInput)
    new CatchExceptioned(uri, anException) {
      inputParcel = Some(anInput)
    }
  }

  private def get_uri(input: G3Parcel) = {
    input.content match {
      case msg: G3Command => msg.uri
      case msg: G3Event => msg.uri
      case _ => "Unknown"
    }
  }
}
